module necrophilia.skeleton.Skeleton;
import necrophilia.skeleton.Bone;
import maths.Matrix;
import necrophilia.helpers.Callback;
import maths.CoordSys;
import necrophilia.skeleton.DataNodes;
import necrophilia.animation.Instance;
import necrophilia.animation.Inteligent;
import necrophilia.animation.Cache;
import utils.Memory;
//import necrophilia.skeleton.Sectors;

class SkeletonCentral(Skeleton){
	private Skeleton [] skeletons;
	private CoordSys delegate()[] getCoordSys;
	
	void register( Skeleton  skeleton){
		skeleton.centralIndex=skeletons.length;
		skeletons~=skeleton;
		getCoordSys.length=getCoordSys.length+1;
	}
	
	void unregister(int key){
		skeletons[skeletons.length-1].centralIndex=key;
		//skeletons[skeletons.length-1].makeReady;
		skeletons[key]=skeletons[skeletons.length-1];
		skeletons.length=skeletons.length-1;
		
		getCoordSys[key]=getCoordSys[getCoordSys.length-1];
		getCoordSys.length=getCoordSys.length-1;
		
	}
	
	void update(){
		foreach(index, skeleton;skeletons) {
			if(getCoordSys[index] is null) skeleton.update();
				else skeleton.update(getCoordSys[index]());
		}
	}
	
	void setGetCoord(Skeleton s, CoordSys delegate() del){
		foreach(index, inout meth; getCoordSys){
			if (s is skeletons[index]){
				meth=del;
				return;
			}
		}
		assert(false);
	}
}

class Skeleton(Anim, Bone){
	
	static assert(is(Anim: AnimationInstance!(Anim.TimerType)));
	alias Callback!(CoordSys, CoordDump*) PositionMenagment;
	alias Callback!(Anim, char[]) AnimationClone;
	private alias Callback!(typeof(this)) RawSkeleton;
	
	CoordSys coordSys;
	Bone!(Anim)* pelvis;
	Bone!(Anim)[] bones;
	static if(is(Anim: InteligentAnimationInstance!(Anim.TimerType))) 
		CoordSys[] firsts;
	mat4[] rotandtrans;
	mat4[] rotonly;
	protected Anim[] animations;
	protected Anim standard;
	protected PositionMenagment menagePosition;
	protected static AnimationClone animationClone;
	package int centralIndex;
	static SkeletonCentral!(typeof(this)) central;	
	
	private static RawSkeleton rawSkeleton;
	
	this(){
		central.register(this);
	}
	
	~this(){
		rotandtrans.free();
		rotonly.free();
		static if(is(Anim: InteligentAnimationInstance!(Anim.TimerType))) 
			firsts.free();
		
	}
	
	void set(CoordSys delegate (CoordDump*) del){
		menagePosition.set(del);
	}
	
	void set(CoordSys function (CoordDump*) func){
		menagePosition.set(func);
	}
	
	static void set(Anim delegate (char[]) del){
		animationClone.set(del);
	}
	
	static void set(Anim function (char[]) func){
		animationClone.set(func);
	}
	
	static void set(typeof(this) delegate () del){
		rawSkeleton.set(del);
	}
	
	static void set(typeof(this) function () func){
		rawSkeleton.set(func);
	}
	
	void update(CoordSys modelSys=CoordSys.identity){
		//if(centralIndex == 1) return;
		
		
		CoordSys coordSys= this.coordSys in modelSys;
		standard.getStandardFirst;
		
		foreach(inout bone;bones)
			bone.reset;
		foreach(anim;animations){
			static if (is(Anim: InteligentAnimationInstance!(Anim.TimerType))){
				anim.work;
			} else {
				anim.update;
				// TODO, add position getter
			}
		}
		foreach(inout bone;bones){
			bone.moveTo(menagePosition.call(&bone.coordDump));
		}
		pelvis.nest(coordSys);
		foreach(index, inout bone;bones){
			rotandtrans[index]= bone.coordSys.getMatrix;
			firsts[index].invert;
			rotonly[index]= (bone.coordSys in firsts[index]).rotation.getMatrix;
		}
		
		//writefln(centralIndex, " ", animations[0].isOn, " ", animations[1].isOn);
	}
	
	void addAnimation(Anim animation){
		foreach(inout bone;bones)
			bone.registerToAnimation(animation);
		animations~=animation;
		static if(is(Anim: InteligentAnimationInstance!(Anim.TimerType)))  
			animation.set(&this.informAboutTheFirst);
			
		if(!animation.isCalculated){
		
			foreach(index,inout bone;bones)
				bone.coordSys=animation.getFirst(index);
				
			pelvis.nest(coordSys);
			foreach(index,inout bone;bones){
				animation.setFirst(index,bone.coordSys);
				//bone.reset;
			}
			
			
			
			//writefln(animation.name," calculated! ", animation.isCalculated);
			animation.setCalculated();
			//getchar;
		}
		
	}
	
	void setStandardAnimation(char[] name){
		foreach(animation;animations){
			if(animation.name==name){
				standard=animation;
				return;
			}
		}
		assert(false,name);
	}
	
	void setAnimation(char[] name, bool on){
		foreach(animation;animations){
			if(animation.name==name){
				if(on) animation.turnOn;
					else animation.turnOff;
				return;
			}
		}
		assert(false,name);
	}
	
	static if(is(Anim: InteligentAnimationInstance!(Anim.TimerType)))  {
		void informAboutTheFirst(int index, CoordSys coords){
			firsts[index]=coords;
		}
	}
	
	Skeleton cloneSkeleton(){
		Skeleton twin= rawSkeleton.call();
		static if(is(Anim: InteligentAnimationInstance!(Anim.TimerType)))  {
			twin.firsts=this.firsts.clone();
		}
		twin.coordSys=this.coordSys;
		twin.rotonly=this.rotonly.clone();
		twin.rotandtrans=this.rotandtrans.clone();
		twin.bones=new Bone[this.bones.length];
		foreach(index, bone; this.bones){
			twin.bones[index]=bone.clone();
		}
		twin.pelvis=&twin.bones[0];
		twin.pelvis.buildReferences(twin.bones);
		
		foreach(animation;this.animations){
			twin.addAnimation(animationClone.call(animation.name));
		}
		
		return twin;
	}
	
	void setAnimationTime(char[] name, Anim.Time time){
		foreach(animation;animations){
			if(animation.name==name){
				animation.setTime(time);
				return;
			}
		}
		assert(false,name);
	}
	
	void setAdaptibilities(float t,float r){
		foreach(inout bone; bones){
			bone.setAdaptibilities(t,r);
		}
	}
	
	/+private SectoredView!(Anim) sectors;
	
	void buildSectors(char[][] parts){
		sectors=new SectoredView!(Anim)(parts);
	}
	
	void addSectorAlias(char[] name, char[][] sectors){
		this.sectors.addGroup(name,sectors);
	}+/
}





import std.stdio;

unittest {
	
}